try:
	from django.contrib.gis.db.models import GeoManager as DjangoManager
except ImportError:
	from django.db.models import Manager as DjangoManager

from django.db.transaction import commit_manually
from django.db import connection
from time import time
import RDF

class Manager(DjangoManager):
	def contribute_to_class(self, django_model, name):
		DjangoManager.contribute_to_class(self, django_model, name)
		setattr(django_model, "about", self._about)
		setattr(django_model, "annotate", self._annotate)
		
	def rdf_get_or_create(self, model, resource, update=False, **kw):
		"""
		rdf_get_or_create(model, resource, update=False, **kw)
		
		Get or create an instance of this model from the
		data for the given resource present in RDF model
		(model parameter). If the instance already exists
		it is simply returned unless the optional third
		argument (update) is True in which case its
		properties are updated as well.

		The properties to update are gleaned from the
		_meta.rdf.properties attribute on the Django model.
		Normally these are created with an rdf.Schema class
		decorator.
		"""
		if isinstance(resource, RDF.Node):
			assert(resource.is_resource())
			uri = resource.uri
		else:
			uri = resource

		try:
			obj = self.get(uri = str(uri), **kw)
			created = False
		except self.model.DoesNotExist:
			obj = self.model(uri = str(uri), **kw)
			created = True

		if created or update:
			for attr, impl in self.model._meta.rdf.properties:
#				print attr
				val = impl(model, resource)
#				print "...", val
				setattr(obj, attr, val)
#			print "done"
			obj.save()
#		print "return"

		return obj, created

	@commit_manually
	def rdf_filter(self, predicate, object):
		"""
		rdf_filter(predicate, object)
		
		Pre-filter the Django ORM query according to resources
		present in the RDF store matching the given predicate
		and object. Predicate can be a string, in which case it
		is transformed into a URI.

		Returns a QuerySet instance containing only matching
		objects that can be further filtered according to the
		normal Django convention.

		This function uses a TEMPORARY TABLE cleared on COMMIT
		and so requires an explicit call to
		
		django.db.transaction.commit()

		if there is no write operation in the current transaction.
		"""
		model = self.model._meta.rdf.model
		if isinstance(predicate, str):
			predicate = RDF.Uri(predicate)
		statement = RDF.Statement(None, predicate, object)
		tdesc = self._rdf_query()
		for r in model.find_statements(statement):
			self._rdf_append(tdesc, r.subject)
		return self._rdf_extra(tdesc)

	@commit_manually
	def sparql_filter(self, q, resource="resource"):
		"""
		sparql_filter(q, resource="resource")
		
		Pre-filter the Django ORM query according to resources
		present in the RDF store returned by the given SPARQL
		query. The SPARQL query is assumed to return a parameter
		named "resource" in each row (configurable via the
		"resource" key-word argument to this function.

		Returns a QuerySet as with the rdf_filter method.

		This function uses a TEMPORARY TABLE cleared on COMMIT
		and so requires an explicit call to
		
		django.db.transaction.commit()

		if there is no write operation in the current transaction.
		"""
		model = self.model._meta.rdf.model
		tdesc = self._rdf_query()
		for r in RDF.SPARQLQuery(str(q)).execute(model):
			self._rdf_append(tdesc, r[resource])
		return self._rdf_extra(tdesc)

	### the following private functions prepare, construct and
	### execute the SQL queries for RDF-to-SQL glue
	def _rdf_query(self):
		table = "rdf_join_%s" % time()
		sql = """\
                CREATE TEMPORARY TABLE "%s" (
		        resource_uri VARCHAR(200) PRIMARY KEY
		) ON COMMIT DROP;""" % (table,)
		return table, sql, {}
	def _rdf_append(self, table_desc, resource):
		assert(resource.is_resource())
		table, sql, args = table_desc
		args[str(resource.uri)] = 'INSERT INTO "%s" VALUES (%%s);' % table
	def _rdf_extra(self, table_desc):
		table, sql, args = table_desc
		resources = args.keys()
		for k in resources:
			sql = sql + "\n" + args[k]
		cursor = connection.cursor()
		cursor.execute(sql, resources)
		tables = [table]
		where = ['"%s".uri = "%s".resource_uri' % (
				self.model._meta.db_table,
				table)]
		return self.extra(tables = tables, where = where)

	### these static methods get attached to instances. The self
	### parameter is the object instance.
	@staticmethod
	def _about(self, predicate = None):
		"""
		about(predicate = None)
		
		Return the statements that exist about this object.
		For example, retrieving all keywords would be done
		as,
		
		obj.about("http://purl.org/dc/elements/1.1/subject")

		The predicate argument is optional, and defaults to
		None -- that is, return every statement about this
		object.
		"""
		model = self._meta.rdf.model
		subject = RDF.Uri(self.uri)
		if isinstance(predicate, str):
			predicate = RDF.Uri(predicate)
		statement = RDF.Statement(subject, predicate, None)
		return model.find_statements(statement)
			
	@staticmethod
	def _annotate(self, predicate, object):
		"""
		annotate(predicate, object)
		
		Annotate this object by adding a statement with the
		given predicate and object into the RDF store.
		"""
		model = self._meta.rdf.model
		subject = RDF.Uri(self.uri)
		if isinstance(predicate, str):
			predicate = RDF.Uri(predicate)
		statement = RDF.Statement(subject, predicate, object)
		model.add_statement(statement)
